package db_utils

import (
	"database/sql"
	"fmt"
	"strings"
	"sync"
)

type dbconfig_item struct {
	id                         string
	connstring                 string
	drivername                 string // db(register name)
	dbtype                     string // mssql, sqlite, mysql
	field_prefix, field_suffix string
}

type dbconn_item struct {
	changDNA     int32 // 改变DNA
	lock         sync.RWMutex
	id           string
	datasourceid string
	repldbname   string
	db           *sql.DB
	config       *dbconfig_item
}

var (
	dbconfig_lock = new(sync.RWMutex)
	dbconfig_map  = make(map[string]*dbconfig_item)
	dbconn_lock   = new(sync.RWMutex)
	dbconn_map    = make(map[string]*dbconn_item)
)

func GetDBConnStats(dbconnid string) *sql.DBStats {
	dbconn_lock.RLock()
	defer dbconn_lock.RUnlock()
	itm := dbconn_map[dbconnid]
	if itm == nil {
		return nil
	}
	if itm.db == nil {
		return nil
	}
	stat := itm.db.Stats()
	return &stat
}

func ResetDBConn(dbconnid string) {
	dbconn_lock.RLock()
	defer dbconn_lock.RUnlock()
	itm := dbconn_map[dbconnid]
	if itm == nil {
		return
	}
	if itm.db == nil {
		return
	}
}

func GetDBConnStatsText(dbconnid string) string {
	stat := GetDBConnStats(dbconnid)
	if stat == nil {
		return ""
	}
	return fmt.Sprintf("max:%d, open:%d, use:%d, idle:%d", stat.MaxOpenConnections, stat.OpenConnections, stat.InUse, stat.Idle)

}

func checkCreateDBConn(id, datasourceid string) (rval *dbconn_item) {
	dbconn_lock.RLock()
	rval = dbconn_map[id]
	dbconn_lock.RUnlock()
	if rval != nil {
		return
	}

	dbconn_lock.Lock()
	rval = dbconn_map[id]
	if rval == nil {
		rval = &dbconn_item{id: id, datasourceid: datasourceid}
		dbconn_map[id] = rval
	}
	dbconn_lock.Unlock()
	return
}

/*
*

	返回的DB不要使用db.Close()进行关闭
*/
func OpenDBConnWithChangeDNA(id string) (*sql.DB, *int32, error) {
	itm := checkCreateDBConn(id, id)
	if itm.db != nil && itm.datasourceid == id {
		return itm.db, &itm.changDNA, nil
	}

	itm.lock.Lock()
	defer itm.lock.Unlock()
	if itm.db != nil && itm.datasourceid == id {
		return itm.db, &itm.changDNA, nil
	}
	itm.datasourceid = id
	itm.config = getDatasourceConfig(id)
	if itm.config == nil {
		return nil, nil, fmt.Errorf("Datasource[%s] not config!", id)
	}
	rval, err := sql.Open(itm.config.drivername, itm.config.connstring)
	if err == nil {
		itm.db = rval
	}
	return rval, &itm.changDNA, err
}

/*
*

	返回的DB不要使用db.Close()进行关闭
*/
func OpenDBConn(id string) (*sql.DB, error) {
	rval, _, err := OpenDBConnWithChangeDNA(id)
	return rval, err
}

func OpenDBConnExWithChangeDNA(id, datasourceid, replname string) (*sql.DB, *int32, error) {
	itm := checkCreateDBConn(id, datasourceid)
	if itm.db != nil && itm.datasourceid == datasourceid && itm.repldbname == replname {
		return itm.db, &itm.changDNA, nil
	}

	itm.lock.Lock()
	defer itm.lock.Unlock()
	if itm.db != nil && itm.datasourceid == datasourceid && itm.repldbname == replname {
		return itm.db, &itm.changDNA, nil
	}
	itm.datasourceid = datasourceid
	itm.repldbname = replname
	itm.config = getDatasourceConfig(datasourceid)
	if itm.config == nil {
		return nil, nil, fmt.Errorf("Datasource[%s] not config!", datasourceid)
	}
	connstring := strings.ReplaceAll(itm.config.connstring, "$dbname$", replname)
	rval, err := sql.Open(itm.config.drivername, connstring)
	if err == nil {
		itm.db = rval
	}
	return rval, &itm.changDNA, err
}

func OpenDBConnEx(id, datasourceid, replname string) (*sql.DB, error) {
	rval, _, err := OpenDBConnExWithChangeDNA(id, datasourceid, replname)
	return rval, err
}

func resetDBConn(itm *dbconn_item) {
	itm.lock.Lock()
	defer itm.lock.Unlock()
	if itm.db != nil {
		itm.db.Close()
		itm.db = nil
	}
	itm.config = nil
	itm.changDNA++

}

func ResetDBConnByDatasourceid(datasourceid string) {
	dbconn_lock.RLock()
	defer dbconn_lock.RUnlock()
	for _, itm := range dbconn_map {
		if itm.datasourceid == datasourceid {
			resetDBConn(itm)
		}
	}
}

func ConfigDBConnMaxOpen(id string, val int) bool {
	dbconn_lock.RLock()
	itm := dbconn_map[id]
	dbconn_lock.RUnlock()
	if itm == nil {
		return false
	}
	itm.lock.Lock()
	defer itm.lock.Unlock()
	if itm.db == nil {
		return false
	}

	itm.db.SetMaxOpenConns(val)
	return true
}

func RemoveDatasource(id string) bool {
	dbconfig_lock.Lock()
	defer dbconfig_lock.Unlock()
	itm := dbconfig_map[id]
	if itm == nil {
		return false
	}
	delete(dbconfig_map, id)
	ResetDBConnByDatasourceid(id)
	return true
}

func getDatasourceConfig(id string) *dbconfig_item {
	dbconfig_lock.RLock()
	defer dbconfig_lock.RUnlock()
	return dbconfig_map[id]
}

func GetDatasourceConnstring(id string) string {
	itm := getDatasourceConfig(id)
	if itm == nil {
		return ""
	}
	return itm.connstring
}

func GetDatasourceDriverName(id string) string {
	itm := getDatasourceConfig(id)
	if itm == nil {
		return ""
	}
	return itm.drivername
}

func GetDatasourceDBType(id string) string {
	itm := getDatasourceConfig(id)
	if itm == nil {
		return ""
	}
	return itm.dbtype
}

func GetDatasourceFieldPrefixAndSuffix(id string) (string, string) {
	itm := getDatasourceConfig(id)
	if itm != nil {
		return itm.field_prefix, itm.field_suffix
	} else {
		return "", ""
	}
}

func ConfigDatasource(id string, connstring, drivername, dbtype, field_prefix, field_suffix string) bool {
	dbconfig_lock.Lock()
	defer dbconfig_lock.Unlock()
	itm := dbconfig_map[id]
	if itm == nil {
		itm = &dbconfig_item{
			id:           id,
			connstring:   connstring,
			drivername:   drivername,
			dbtype:       dbtype,
			field_prefix: field_prefix,
			field_suffix: field_suffix,
		}
		dbconfig_map[id] = itm
		return true
	} else {
		changed := false
		if itm.connstring != connstring {
			itm.connstring = connstring
			changed = true
		}

		if itm.drivername != drivername {
			itm.drivername = drivername
			changed = true
		}

		if itm.dbtype != dbtype {
			itm.dbtype = dbtype
			changed = true
		}

		itm.field_prefix = field_prefix
		itm.field_suffix = field_suffix

		if changed {
			ResetDBConnByDatasourceid(id)
		}

		return changed
	}
}
